﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;
using TestXNA3.Resources.Shaders;
using ModelImporter;
using TestXNA3.Render;

namespace TestXNA3.Resources.Meshes
{
    public class StaticMesh : MeshResource
    {
        public Model Mesh
        {
            get { _mesh.LastTimeUsed = GameStartup.StaticGameStartup.GameInfo.TimeSeconds; return _mesh.Active; }
        }

        private ResourceManager.ManagedResource<Model> _mesh = null;

        // Resource management :)
        private struct MeshDesc
        {
            public string MeshName;
            
            public ResourceManager.ManagedResource<Model> Mesh;
            //public ModelTestDesc Desc;
            public Vector3 Bounds;
            
            public StillDesign.PhysX.ConvexMesh ConvexMesh;
            
            public StillDesign.PhysX.TriangleMesh TriMesh;
            
            public ModelTestDesc.Triangle[] TraceTriangles;
        }
        private static List<MeshDesc> _cachedMeshes = null;
        private static ShaderResource _defaultShader= null;

        // Convex mesh
        
        private StillDesign.PhysX.ConvexMesh _convexMesh;
        [System.ComponentModel.Browsable(false)]
        public StillDesign.PhysX.ConvexMesh ConvexMesh
        {
            get { return _convexMesh; }
            set { _convexMesh = value; }
        }

        // Triangle mesh
        
        private StillDesign.PhysX.TriangleMesh _triMesh;
        [System.ComponentModel.Browsable(false)]
        public StillDesign.PhysX.TriangleMesh TriMesh
        {
            get { return _triMesh; }
            set { _triMesh = value; }
        }

        // Raw tree soup, very slow
        
        private ModelTestDesc.Triangle[] _traceTriangles;
        [System.ComponentModel.Browsable(false)]
        public ModelTestDesc.Triangle[] TraceTriangles
        {
            get { return _traceTriangles; }
        }

        private Vector3 _bounds;
        [System.ComponentModel.Browsable(false)]
        public Vector3 Bounds
        {
            get { return _bounds; }
        }

        private string _meshName;
        public string MeshName
        {
            get { return _meshName; }
        }

        public StaticMesh(string meshName)
        {
            if (meshName.Length > 0)
                SetMesh(meshName);
        }

        public void SetMesh(string meshName)
        {
            meshName = TestConfig.ContentPath + "StaticMeshes/" + meshName;
            if (_cachedMeshes == null)
            {
                _cachedMeshes = new List<MeshDesc>();
            }
            if (_defaultShader == null)
            {
                _defaultShader = new StaticMeshDefaultShader();
            }
            Shader = new StaticMeshDefaultShader();
            try
            {
                SetModel(meshName);
            }
            catch
            {
                Logger.Print("Can't find static mesh " + meshName);
            }
        }

        private void SetMeshByDesc(MeshDesc desc)
        {
            _mesh = desc.Mesh;
            _triMesh = desc.TriMesh;
            _convexMesh = desc.ConvexMesh;
            _bounds = desc.Bounds;
            _traceTriangles = desc.TraceTriangles;
            _meshName = desc.MeshName;
        }

        private void SetModel(string meshName)
        {
            foreach (MeshDesc desc in _cachedMeshes)
            {
                if (desc.MeshName == meshName.ToLower())
                {
                    SetMeshByDesc(desc);
                    return;
                }
            }
            ResourceManager.ManagedResource<Model> mesh = GameStartup.ContentManager.LoadManagedModel(meshName);
            if (mesh != null)
            {
                MeshDesc desc = new MeshDesc();
                desc.Mesh = mesh;
                desc.MeshName = meshName.ToLower();
                desc.ConvexMesh = GameStartup.PhysXCore.CreateConvexMesh(((ModelTestDesc)mesh.Original.Tag).ConvexMesh);
                desc.TriMesh = GameStartup.PhysXCore.CreateTriangleMesh(((ModelTestDesc)mesh.Original.Tag).TriMesh);
                desc.Bounds = ((ModelTestDesc)mesh.Original.Tag).BoundsMax - ((ModelTestDesc)mesh.Original.Tag).BoundsMin;
                desc.TraceTriangles = ((ModelTestDesc)mesh.Original.Tag).Triangles;
                SetMeshByDesc(desc);
                _cachedMeshes.Add(desc);
            }
            //((StaticMeshDefaultShader)Shader).Diffuse = new TextureResource("default");
            //((StaticMeshDefaultShader)Shader).Normal = new TextureResource("default_normal");
        }

        public override void Render(ref Matrix view, ref Matrix proj, ref Vector3 ViewerLocation,SceneRenderer.EDrawMode drawMode)
        {
            Shader.World = World;
            Shader.View = view;
            Shader.Projection = proj;
            Shader.EyePosition = new Vector4(ViewerLocation, 1.0f);
            Shader.SetTechnique(drawMode);
            Shader.ApplyShaderConst();

            DrawDefaultModel(Mesh);
        }

        public bool Trace(Vector3 start, Vector3 end, ref float T)
        {
            Matrix invWorld = Matrix.Invert(World);
            Vector3 localStart = Vector3.Transform(start, invWorld);
            Vector3 localEnd = Vector3.Transform(end, invWorld);
            Vector3 localDir = localEnd - localStart;
            localDir.Normalize();
            Ray ray = new Ray(localStart, localDir);
            for (int i = 0; i < TraceTriangles.Length; i++)
            {
                float t = 0.0f, u = 0.0f, v = 0.0f;
                if (RayTriangleIntersect(ray.Position, ray.Direction
                    , TraceTriangles[i].p0, TraceTriangles[i].p1, TraceTriangles[i].p2, out t, out u, out v))
                {
                    //if (t < T)
                    {
                        Vector3 p0 = Vector3.Transform(TraceTriangles[i].p0, World);
                        Vector3 p1 = Vector3.Transform(TraceTriangles[i].p1, World);
                        Vector3 p2 = Vector3.Transform(TraceTriangles[i].p2, World);

                        // World ray check
                        Vector3 dir = end - start;
                        dir.Normalize();
                        Ray worldRay = new Ray(start, dir);
                        if (RayTriangleIntersect(worldRay.Position, worldRay.Direction, p0, p1, p2, out t, out u, out v))
                        {
                            if (t < T)
                            {
                                T = t;
                                return true;
                            }
                        }
                    }
                }
            }

            return false;
        }

        // Helper function
        public static bool RayTriangleIntersect(Vector3 ray_origin, Vector3 ray_direction,
                    Vector3 vert0, Vector3 vert1, Vector3 vert2,
                    out float t, out float u, out float v)
        {
            t = 0; u = 0; v = 0;

            Vector3 edge1 = vert1 - vert0;
            Vector3 edge2 = vert2 - vert0;

            Vector3 tvec, pvec, qvec;
            float det, inv_det;

            pvec = Vector3.Cross(ray_direction, edge2);

            det = Vector3.Dot(edge1, pvec);

            if (det > -0.00001f)
                return false;

            inv_det = 1.0f / det;

            tvec = ray_origin - vert0;

            u = Vector3.Dot(tvec, pvec) * inv_det;
            if (u < -0.001f || u > 1.001f)
                return false;

            qvec = Vector3.Cross(tvec, edge1);

            v = Vector3.Dot(ray_direction, qvec) * inv_det;
            if (v < -0.001f || u + v > 1.001f)
                return false;

            t = Vector3.Dot(edge2, qvec) * inv_det;

            if (t <= 0)
                return false;

            return true;
        }
    }
}
